package cn.lyf.currey;

import java.util.Optional;
import java.util.function.*;
import cn.lyf.currey.Tuple.*;

public class Currying {

    // 有返回值
    public static <T, U, R> f1<T, R> currying(BiFunction<T, U, R> bi, final U arg) {
        return a -> bi.apply(a, arg);
    }

    public static <T, U, R> f1<U, R> currying(final T arg, BiFunction<T, U, R> bi) {
        return a -> bi.apply(arg, a);
    }

    public static <T, R> Lazy<R> currying(final T arg, Function<T, R> f) {
        return () -> f.apply(arg);
    }

    public static <T, R> Lazy<R> currying(Function<T, R> f, final T arg) {
        return () -> f.apply(arg);
    }

    public static <R> R currying(Supplier<R> s) {
        return s.get();
    }

    // 无返回值
    public static <T, U> Consumer<U> currying(final T arg, BiConsumer<T, U> bc) {
        return u -> bc.accept(arg, u);
    }

    public static <T, U> Consumer<T> currying(BiConsumer<T, U> bc, final U arg) {
        return t -> bc.accept(t, arg);
    }

    public static <T> Runnable currying(final T arg, Consumer<T> c) {
        return () -> c.accept(arg);
    }

    public static void currying(Runnable r) {
        r.run();
    }

    // f系 扩展接口
    public interface f5<A, B, C, D, E, F> {
        F apply(A a, B b, C c, D d, E e);

        default f4<B, C, D, E, F> apply(A a) {
            return currying(a, this);
        }

        default f5<A, B, C, D, E, F> patch(
                f5<? super A, ? super B, ? super C, ? super D, ? super E, Optional<Tuple5<? extends A, ? extends B, ? extends C, ? extends D, ? extends E>>> limiter,
                f5<? super A, ? super B, ? super C, ? super D, ? super E, ? extends F> f) {
            return (a, b, c, d, e) -> Tuple1.of(limiter.apply(a, b, c, d, e))
                    .map(t -> f.apply(t._1, t._2, t._3, t._4, t._5), () -> apply(a, b, c, d, e));
        }

        default <G> f5<A, B, C, D, E, G> pipe(Function<? super F, ? extends G> after) {
            return (a, b, c, d, e) -> after.apply(apply(a, b, c, d, e));
        }
    }

    public interface f4<A, B, C, D, E> {
        E apply(A a, B b, C c, D d);

        default f3<B, C, D, E> apply(A a) {
            return currying(a, this);
        }

        default f4<A, B, C, D, E> patch(
                f4<? super A, ? super B, ? super C, ? super D, Optional<Tuple4<? extends A, ? extends B, ? extends C, ? extends D>>> limiter,
                f4<? super A, ? super B, ? super C, ? super D, ? extends E> f) {
            return (a, b, c, d) -> Tuple1.of(limiter.apply(a, b, c, d)).map(t -> f.apply(t._1, t._2, t._3, t._4),
                    () -> apply(a, b, c, d));
        }

        default <F> f4<A, B, C, D, F> pipe(Function<? super E, ? extends F> after) {
            return (a, b, c, d) -> after.apply(apply(a, b, c, d));
        }
    }

    public interface f3<A, B, C, D> {
        D apply(A a, B b, C c);

        default f2<B, C, D> apply(A a) {
            return currying(a, this);
        }

        default f3<A, B, C, D> patch(
                f3<? super A, ? super B, ? super C, Optional<Tuple3<? extends A, ? extends B, ? extends C>>> limiter,
                f3<? super A, ? super B, ? super C, ? extends D> f) {
            return (a, b, c) -> Tuple1.of(limiter.apply(a, b, c)).map(t -> f.apply(t._1, t._2, t._3),
                    () -> apply(a, b, c));
        }

        default <E> f3<A, B, C, E> pipe(Function<? super D, ? extends E> after) {
            return (a, b, c) -> after.apply(apply(a, b, c));
        }
    }

    public interface f2<A, B, C> extends BiFunction<A, B, C> {
        default f1<B, C> apply(A a) {
            return currying(a, this);
        }

        default f2<A, B, C> patch(BiFunction<? super A, ? super B, Optional<Tuple2<? extends A, ? extends B>>> limiter,
                BiFunction<? super A, ? super B, ? extends C> f) {
            return (a, b) -> Tuple1.of(limiter.apply(a, b)).map(t -> f.apply(t._1, t._2), () -> apply(a, b));
        }

        default <D> f2<A, B, D> pipe(Function<? super C, ? extends D> after) {
            return (a, b) -> after.apply(apply(a, b));
        }
    }

    public interface f1<A, B> extends Function<A, B> {

        default f1<A, B> patch(Function<? super A, Optional<? extends A>> limiter, Function<? super A, B> f) {
            return a -> Tuple1.of(limiter.apply(a)).map(f::apply, () -> apply(a));
        }

        default <C> f1<A, C> pipe(Function<? super B, ? extends C> after) {
            return a -> after.apply(apply(a));
        }
    }

    public interface Lazy<A> extends Supplier<A> {

        default Lazy<A> patch(Supplier<? extends A> g) {
            return () -> {
                A a = get();
                return a == null ? g.get() : a;
            };
        }

        default <B> Lazy<B> pipe(Function<? super A, ? extends B> after) {
            return () -> after.apply(get());
        }
    }

    public static <A, B, C, D> f2<A, B, D> currying(f3<A, B, C, D> f, final C c) {
        return (a, b) -> f.apply(a, b, c);
    }

    public static <A, B, C, D> f2<B, C, D> currying(final A a, f3<A, B, C, D> f) {
        return (b, c) -> f.apply(a, b, c);
    }

    public static <A, B, C, D, E> f3<A, B, C, E> currying(f4<A, B, C, D, E> f, final D d) {
        return (a, b, c) -> f.apply(a, b, c, d);
    }

    public static <A, B, C, D, E> f3<B, C, D, E> currying(final A a, f4<A, B, C, D, E> f) {
        return (b, c, d) -> f.apply(a, b, c, d);
    }

    public static <A, B, C, D, E, F> f4<A, B, C, D, F> currying(f5<A, B, C, D, E, F> f, final E e) {
        return (a, b, c, d) -> f.apply(a, b, c, d, e);
    }

    public static <A, B, C, D, E, F> f4<B, C, D, E, F> currying(final A a, f5<A, B, C, D, E, F> f) {
        return (b, c, d, e) -> f.apply(a, b, c, d, e);
    }

    // c系 扩展接口
    public interface c5<A, B, C, D, E> {
        void accept(A a, B b, C c, D d, E e);
    }

    public interface c4<A, B, C, D> {
        void accept(A a, B b, C c, D d);
    }

    public interface c3<A, B, C> {
        void accept(A a, B b, C c);
    }

    public static <A, B, C> BiConsumer<A, B> currying(c3<A, B, C> f, C c) {
        return (a, b) -> f.accept(a, b, c);
    }

    public static <A, B, C> BiConsumer<B, C> currying(A a, c3<A, B, C> f) {
        return (b, c) -> f.accept(a, b, c);
    }

    public static <A, B, C, D> c3<B, C, D> currying(A a, c4<A, B, C, D> f) {
        return (b, c, d) -> f.accept(a, b, c, d);
    }

    public static <A, B, C, D> c3<A, B, C> currying(c4<A, B, C, D> f, D d) {
        return (a, b, c) -> f.accept(a, b, c, d);
    }

    public static <A, B, C, D, E> c4<A, B, C, D> currying(c5<A, B, C, D, E> f, E e) {
        return (a, b, c, d) -> f.accept(a, b, c, d, e);
    }

    public static <A, B, C, D, E> c4<B, C, D, E> currying(A a, c5<A, B, C, D, E> f) {
        return (b, c, d, e) -> f.accept(a, b, c, d, e);
    }
}
